using Volo.Abp.Domain.Repositories;
using Volo.Abp.Validation;

using WL.Think.Abp.Dto;
using WL.Think.Abp.Entities;
using WL.Think.Abp.Repositories;

namespace WL.Think.Abp.Services;

public abstract class BaseCategoryAppService<TEntity, TGetOutputDto, TGetListInput, TCreateInput, TUpdateInput> :
    ThinkAppService<TEntity, TGetOutputDto, TGetListInput, TCreateInput, TUpdateInput>,
    IBaseCategoryAppService<TGetOutputDto, TGetListInput, TCreateInput, TUpdateInput>
    where TEntity : BaseCategory<TEntity,Guid>
    where TGetOutputDto : BaseCategoryDto<TGetOutputDto>
    where TGetListInput : BasePagedDto
    where TCreateInput : BaseCategoryCreateUpdateDto
    where TUpdateInput : BaseCategoryCreateUpdateDto
{
    protected new IBaseCategoryRepository<TEntity, Guid> Repository;
    protected virtual string? MovePolicyName { get; set; }

    protected BaseCategoryAppService(IBaseCategoryRepository<TEntity, Guid> repository) : base(repository)
    {
        Repository = repository;
    }

    public override async Task<TGetOutputDto> CreateAsync(TCreateInput input)
    {
        await CheckCreatePolicyAsync();
        TEntity? parent = null;
        if(input.ParentId != null)
        {
            parent = await Repository.GetAsync(input.ParentId.Value);
            if(parent == null)
            {
                throw new AbpValidationException(L["The selected parent category does not exist"]);
            }
        }
        var entity = await MapToEntityAsync(input);

        entity.IsActive = true;
        entity.SetParent(parent);
        TryToSetTenantId(entity);
        await BeforeCreateHookAsync(entity, input);
        await Repository.InsertAsync(entity, true);
        await AfterCreateHookAsync(entity, input);
        return await MapToGetOutputDtoAsync(entity);
    }

    public async Task<List<TGetOutputDto>> GetActiveTreeAsync(Guid? parentId = null)
    {
        var items = await Repository.GetActiveTreeAsync(parentId);
        return await MapToGetListOutputDtosAsync(items);
    }

    public async Task<List<TGetOutputDto>> GetTreeAsync(Guid? parentId = null)
    {
        var items = await Repository.GetTreeAsync(parentId);
        return await MapToGetListOutputDtosAsync(items);
    }

    public async Task MoveAsync(BaseCategoryMoveDto input)
    {
        if (!input.IsCanMoving()) return;

        await CheckMovePolicyAsync();

        var item = await Repository.GetAsync(input.MoveId, false);
        var dest = await Repository.GetAsync(input.DestId, false);
        var parent = dest;
        var index = item.Index;

        await BeforeMoveHookAsync(item, dest, input);
        switch (input.Position)
        {
            case MovePositionEnum.INSIDE:
                index = await Repository.GetMaxIndexAsync(input.DestId);
                index += 1;
                break;
            case MovePositionEnum.BEFORE:
                parent = await Repository.GetAsync(dest.ParentId!.Value);
                index = dest.Index - 1;
                break;
            case MovePositionEnum.AFTER:
                parent = await Repository.GetAsync(dest.ParentId!.Value);
                index = dest.Index + 1;
                break;
        }

        item.SetParent(parent, index);

        if (parent.Children!.Count != 0)
        {
            //TODO: 刷新分类
            var children = parent.Children.OrderBy(x => x.Index).ThenBy(x => x.LastModificationTime);
            index = 0;
            foreach (var child in children)
            {
                child.Index = index;
                index++;
            }
            await Repository.UpdateManyAsync(parent.Children);
        }
        await Repository.UpdateAsync(item, true);
        await AfterMoveHookAsync(item, dest, input);
    }

    public override async Task<TGetOutputDto> UpdateAsync(Guid id, TUpdateInput input)
    {
        await CheckUpdatePolicyAsync();
        if(id == input.ParentId)
        {
            throw new AbpValidationException(L["Cannot select self as parent category"]);
        }

        var entity = await Repository.GetAsync(id);
        var parentId = entity.ParentId;
        var path = entity.Path;
        await MapToEntityAsync(input, entity);
        if(parentId != input.ParentId)
        {
            var parent = input.ParentId == null ? null : await Repository.GetAsync(input.ParentId.Value);
            entity.SetParent(parent);

            var children = await Repository.GetListAsync(s => s.Path.StartsWith(path));
            foreach (var child in entity.Children)
            {
                child.ReplacePath(path, entity.Path);
            }
        }
        await BeforeUpdateHookAsync(entity, input);
        await Repository.UpdateAsync(entity, true);
        await AfterUpdateHookAsync(entity, input);
        return await MapToGetOutputDtoAsync(entity);
    }

    public override async Task DeleteAsync(Guid id)
    {
        await CheckDeletePolicyAsync();
        
        if(await Repository.AnyAsync(x=>x.ParentId == id))
        {
            throw new AbpValidationException(L["This category contains sub categories"]);
        }
        
        var entity = await Repository.GetAsync(id);
        if(entity.Builtin)
        {
            throw new AbpValidationException(L["Built-in category cannot be deleted"]);
        }
        await BeforeDeleteHookAsync(entity);
        await DeleteByIdAsync(entity.Id);
        await AfterDeleteHookAsync(entity);
    }

    /// <summary>
    /// 检查是否具有移动权限
    /// </summary>
    /// <returns></returns>
    protected virtual async Task CheckMovePolicyAsync()
    {
        await CheckPolicyAsync(MovePolicyName);
    }

    /// <summary>
    /// 移动前钩子
    /// </summary>
    /// <param name="entity">实体</param>
    /// <param name="parent">上级</param>
    /// <param name="input">移动参数</param>
    /// <returns></returns>
    protected virtual async Task BeforeMoveHookAsync(TEntity entity, TEntity parent, BaseCategoryMoveDto input)
    {
        await Task.CompletedTask;
    }

    /// <summary>
    /// 移动后钩子
    /// </summary>
    /// <param name="entity">实体</param>
    /// <param name="parent">上级</param>
    /// <param name="input">移动参数</param>
    /// <returns></returns>
    protected virtual async Task AfterMoveHookAsync(TEntity entity, TEntity parent, BaseCategoryMoveDto input)
    {
        await Task.CompletedTask;
    }
}

public abstract class BaseCategoryAppService<TEntity, TGetOutputDto, TGetListInput, TCreateInput> :
    BaseCategoryAppService<TEntity, TGetOutputDto, TGetListInput, TCreateInput, TCreateInput>,
    IBaseCategoryAppService<TGetOutputDto, TGetListInput, TCreateInput>
    where TEntity : BaseCategory<TEntity, Guid>
    where TGetOutputDto : BaseCategoryDto<TGetOutputDto>
    where TGetListInput: BasePagedDto
    where TCreateInput : BaseCategoryCreateUpdateDto
{
    protected BaseCategoryAppService(IBaseCategoryRepository<TEntity, Guid> repository) : base(repository)
    {
    }
}

public abstract class BaseCategoryAppService<TEntity, TGetOutputDto,TCreateInput> :
    BaseCategoryAppService<TEntity, TGetOutputDto, BasePagedDto, TCreateInput>,
    IBaseCategoryAppService<TGetOutputDto, TCreateInput>
    where TEntity : BaseCategory<TEntity, Guid>
    where TGetOutputDto : BaseCategoryDto<TGetOutputDto>
    where TCreateInput: BaseCategoryCreateUpdateDto
{
    protected BaseCategoryAppService(IBaseCategoryRepository<TEntity, Guid> repository) : base(repository)
    {
    }
}

public abstract class BaseCategoryAppService<TEntity, TGetOutputDto> :
    BaseCategoryAppService<TEntity, TGetOutputDto, BaseCategoryCreateUpdateDto>,
    IBaseCategoryAppService<TGetOutputDto>
    where TEntity: BaseCategory<TEntity, Guid>
    where TGetOutputDto : BaseCategoryDto<TGetOutputDto>
{
    protected BaseCategoryAppService(IBaseCategoryRepository<TEntity, Guid> repository) : base(repository)
    {
    }
}
